﻿using System;
using System.Collections.Generic;

namespace QuickBencher
{
    /// <summary>
    /// Used to run benchmarks on peaces of code.
    /// </summary>
    public class Benchmark
    {
        private List<Measurement> _measurements;
        private int? _labelWidth;
        private bool _withGarbageCollection;

        private Benchmark(int? labelWidth) : this(labelWidth, false)
        {   
        }

        public Benchmark(int? labelWidth, bool withGarbageCollection)
        {
            _withGarbageCollection = withGarbageCollection;
            _labelWidth = labelWidth;
            _measurements = new List<Measurement>();
        }

        /// <summary>
        /// Measures the time it takes to run the provided action.
        /// </summary>
        /// <param name="action">Code to execute</param>
        public static Measurement Measure(Action action)
        {
            return Measure(string.Empty, action);
        }

        /// <summary>
        /// Measures the time it takes to run the provided action.
        /// </summary>
        /// <param name="label">Lable added to the output</param>
        /// <param name="action">Code to execute</param>
        public static Measurement Measure(string label, Action action)
        {
            var measurement = RunMeasurement(label, action, false);
            Printers.Get().Print(measurement);
            return measurement;
        }
        
        /// <summary>
        /// Used to run several benchmarks in sequence.
        /// </summary>
        /// <param name="block">Action containing several calls to the Report method.</param>
        public static IEnumerable<Measurement> Bm(Action<Benchmark> block)
        {
            return Bm(null, block);
        }

        /// <summary>
        /// Runs the tests twice, the first time as a rehearsal in order to get 
        /// the runtime environment stable, the second time for real. Garbage 
        /// collection is executed before the start of each of the real timings.
        /// </summary>
        /// <param name="labelWidth">The maximum width of the labels (defaults to 10)</param>
        /// <param name="block">Action containing several calls to the Report() method.</param>
        public static IEnumerable<Measurement> Bm(int? labelWidth, Action<Benchmark> block)
        {
            var benchmark = new Benchmark(labelWidth);
            block.Invoke(benchmark);

            benchmark._measurements.Print();
            return benchmark._measurements;
        }

        /// <summary>
        /// Runs the tests twice, the first time as a rehearsal in order to get 
        /// the runtime environment stable, the second time for real. Garbage 
        /// collection is executed before the start of each of the real timings.
        /// </summary>
        /// <param name="block">Action containing several calls to the Report() method.</param>
        public static IEnumerable<Measurement> Bmbm(Action<Benchmark> block)
        {
            return Bmbm(null, block);
        }

        /// <summary>
        /// Runs the tests twice, the first time as a rehearsal in order to get 
        /// the runtime environment stable, the second time for real. Garbage 
        /// collection is executed before the start of each of the real timings.
        /// </summary>
        /// <param name="labelWidth">The maximum width of the labels (defaults to 10)</param>
        /// <param name="block">Action containing several calls to the Report() method.</param>
        public static IEnumerable<Measurement> Bmbm(int? labelWidth, Action<Benchmark> block)
        {
            var benchmark = new Benchmark(labelWidth);
            block.Invoke(benchmark);
            benchmark._measurements.PrintRehersal();

            benchmark = new Benchmark(labelWidth, true);
            block.Invoke(benchmark);
            benchmark._measurements.Print();
            return benchmark._measurements;
        }

        /// <summary>
        /// Measures the time it takes to run the provided action. Used in conjunction 
        /// with the Bm() method.
        /// </summary>
        /// <param name="actionToReportOn">Code to execute</param>
        public void Report(Action actionToReportOn)
        {
            Report(string.Empty, actionToReportOn);
        }

        /// <summary>
        /// Measures the time it takes to run the provided action. Used in conjunction 
        /// with the Bm() method.
        /// </summary>
        /// <param name="label">Lable added to the output</param>
        /// <param name="actionToReportOn">Code to execute</param>
        public void Report(string label, Action actionToReportOn)
        {
            var measurement = RunMeasurement(label, actionToReportOn, _withGarbageCollection);
            _measurements.Add(measurement);
        }

        private static Measurement RunMeasurement(string label, Action action, bool withGarbageCollection)
        {
            var runner = new BenchmarkRunner(label, action, withGarbageCollection);
            runner.Execute();
            return runner.Measurement;
        }
    }
}